home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Resources / Audio, Video & Photo / Songbird 0.7.0 / Songbird_0.7.0_windows-i686-msvc8.exe / jsmodules / DropHelper.jsm < prev    next >
Text File  |  2008-08-06  |  48KB  |  1,316 lines

  1. /*
  2. //
  3. // BEGIN SONGBIRD GPL
  4. // 
  5. // This file is part of the Songbird web player.
  6. //
  7. // Copyright(c) 2005-2008 POTI, Inc.
  8. // http://songbirdnest.com
  9. // 
  10. // This file may be licensed under the terms of of the
  11. // GNU General Public License Version 2 (the "GPL").
  12. // 
  13. // Software distributed under the License is distributed 
  14. // on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either 
  15. // express or implied. See the GPL for the specific language 
  16. // governing rights and limitations.
  17. //
  18. // You should have received a copy of the GPL along with this 
  19. // program. If not, go to http://www.gnu.org/licenses/gpl.html
  20. // or write to the Free Software Foundation, Inc., 
  21. // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  22. // 
  23. // END SONGBIRD GPL
  24. //
  25.  */
  26.  
  27. /*
  28.  
  29.   DropHelper Module
  30.  
  31.     This module contains three different helpers:
  32.  
  33.     - ExternalDropHander
  34.  
  35.       Used to handle external drops (ie, standard file drag and drop from the
  36.       operating system).
  37.       
  38.     - InternalDropHander
  39.  
  40.       Used to handle internal drops (ie, mediaitems, medialists)
  41.       
  42.     - DNDUtils
  43.  
  44.       Contains methods that are used by both of the helpers, and that are useful for
  45.       drag and drop operations in general.
  46.   
  47. */
  48.  
  49. EXPORTED_SYMBOLS = [ "DNDUtils",
  50.                      "ExternalDropHandler",
  51.                      "InternalDropHandler" ];
  52.  
  53. const Cc = Components.classes;
  54. const Ci = Components.interfaces;
  55. const Cr = Components.results;
  56. const Ce = Components.Exception;
  57. const Cu = Components.utils;
  58.  
  59. Cu.import("resource://app/jsmodules/ArrayConverter.jsm");
  60. Cu.import("resource://app/jsmodules/sbProperties.jsm");
  61. Cu.import("resource://app/jsmodules/SBJobUtils.jsm");
  62.  
  63.  
  64. /*
  65.  
  66.   DNDUtils
  67.   
  68.   This helper contains a number of methods that may be used when implementing
  69.   a drag and drop handler.
  70.   
  71. */
  72.  
  73.  
  74. var DNDUtils = {
  75.  
  76.   _Ci: Components.interfaces,
  77.   _Cc: Components.classes,
  78.  
  79.   // returns true if the drag session contains supported flavors
  80.   isSupported: function(aDragSession, aFlavourArray) {
  81.     for (var i=0;i<aFlavourArray.length;i++) {
  82.       if (aDragSession.isDataFlavorSupported(aFlavourArray[i])) {
  83.         return true;
  84.       }
  85.     }
  86.     return false;
  87.   },
  88.   
  89.   // adds the flavors in the array to the given flavourset
  90.   addFlavours: function(aFlavourSet, aFlavourArray) {
  91.     for (var i=0; i<aFlavourArray.length; i++) {
  92.       aFlavourSet.appendFlavour(aFlavourArray[i]);
  93.     }
  94.   },
  95.  
  96.   // fills an array with the data for all items of a given flavor
  97.   getTransferDataForFlavour: function(aFlavour, aSession, aArray) {
  98.     if (!aSession) {
  99.       var dragService = this._Cc["@mozilla.org/widget/dragservice;1"]
  100.                             .getService(this._Ci.nsIDragService);
  101.       aSession = dragService.getCurrentSession();
  102.     }
  103.  
  104.     var nitems = aSession.numDropItems;
  105.     var r = null;
  106.  
  107.     if (aSession.isDataFlavorSupported(aFlavour)) {
  108.  
  109.       for (var i=0;i<nitems;i++) {
  110.         var transfer = this._Cc["@mozilla.org/widget/transferable;1"]
  111.                            .createInstance(this._Ci.nsITransferable);
  112.  
  113.         transfer.addDataFlavor(aFlavour);
  114.         aSession.getData(transfer, i);
  115.         var data = {};
  116.         var length = {};
  117.         transfer.getTransferData(aFlavour, data, length);
  118.         if (!r) r = data.value;
  119.         if (aArray) aArray.push([data.value, length.value, aFlavour]);
  120.       }
  121.     }
  122.  
  123.     return r;
  124.   },
  125.   
  126.   // similar to getTransferDataForFlavour but adds extraction of the internal
  127.   // drag data from the dndSourceTracker, and queries an interface from the
  128.   // result
  129.   getInternalTransferDataForFlavour: function(aSession, aFlavour, aInterface) {
  130.     var data = this.getTransferDataForFlavour(aFlavour, aSession);
  131.     if (data) 
  132.       return this.getInternalTransferData(data, aInterface);
  133.     return null;
  134.   },
  135.   
  136.   // similar to getTransferData but adds extraction of the internal drag data
  137.   // from the dndSourceTracker, and queries an interface from the result
  138.   getInternalTransferData: function(aData, aInterface) {
  139.     // get the object from the dnd source tracker
  140.     var dnd = Components.classes["@songbirdnest.com/Songbird/DndSourceTracker;1"]
  141.         .getService(this._Ci.sbIDndSourceTracker);
  142.     var source = dnd.getSourceSupports(aData);
  143.     // and request the specified interface
  144.     return source.QueryInterface(aInterface);
  145.   },
  146.  
  147.   // returns an array with the data for any flavour listed in the given array
  148.   getTransferData: function(aSession, aFlavourArray) {
  149.     var data = [];
  150.     for (var i=0;i<aFlavourArray.length;i++) {
  151.       if (this.getTransferDataForFlavour(aFlavourArray[i], aSession, data)) 
  152.         break;
  153.     }
  154.     return data;
  155.   },
  156.   
  157.   // reports a custom temporary message to the status bar
  158.   customReport: function(aMessage) {
  159.     var SB_NewDataRemote = 
  160.       Components.Constructor("@songbirdnest.com/Songbird/DataRemote;1",
  161.                              "sbIDataRemote",
  162.                              "init");
  163.     var statusOverrideText = 
  164.       SB_NewDataRemote( "faceplate.status.override.text", null );
  165.     var statusOverrideType = 
  166.       SB_NewDataRemote( "faceplate.status.override.type", null );
  167.  
  168.     statusOverrideText.stringValue = "";
  169.     statusOverrideText.stringValue = aMessage;
  170.     statusOverrideType.stringValue = "report";
  171.   },
  172.  
  173.   _str_tracksaddedto        : null,
  174.   _str_trackaddedto         : null,
  175.   _str_notracksaddedto      : null,
  176.   _str_trackalreadypresent  : null,
  177.   _str_tracksalreadypresent : null,
  178.  
  179.   // temporarily writes "X tracks added to <name>, Y tracks already present" 
  180.   // in the status bar. if 0 is specified for aDups, the second part of the
  181.   // message is skipped.
  182.   reportAddedTracks: function(aAdded, aDups, aDestName) {
  183.     if (!this._str_tracksaddedto) {
  184.       this._gotStrings = true;
  185.       this._str_tracksaddedto =
  186.         this._getString("library.tracksaddedto", "tracks added to");
  187.       this._str_trackaddedto =
  188.         this._getString("library.trackaddedto", "track added to");
  189.       this._str_notracksaddedto =
  190.         this._getString("library.notracksaddedto", "No tracks added to");
  191.       this._str_trackalreadypresent =
  192.         this._getString("library.trackalreadypresent", "track already present");
  193.       this._str_tracksalreadypresent =
  194.         this._getString("library.tracksalreadypresent","tracks already present");
  195.       this._stringbundle = null;
  196.     }
  197.     var msg = aAdded + " ";
  198.  
  199.     // start message with 'xxx added to'... except for 0 ('no tracks added')
  200.     switch (aAdded) {
  201.       case 0:  msg = this._str_notracksaddedto;  break;
  202.       case 1:  msg += this._str_trackaddedto;    break;
  203.       default: msg += this._str_tracksaddedto;   break;
  204.     }
  205.  
  206.     msg += " " + aDestName;
  207.  
  208.     // append the message about items that were already present (if any)
  209.     if (aDups > 0) {
  210.       msg += " (" + aDups + " ";
  211.       if (aDups == 1) {
  212.         msg += this._str_trackalreadypresent;
  213.       }
  214.       else {
  215.         msg += this._str_tracksalreadypresent;
  216.       }
  217.       msg += ")";
  218.     }
  219.  
  220.     this.customReport(msg);
  221.   },
  222.   
  223.   // reports stats on the statusbar using standard rules for what to show and
  224.   // in which circumstances
  225.   standardReport: function(aTargetList,
  226.                            aImportedInLibrary,
  227.                            aDuplicates,
  228.                            aInsertedInMediaList,
  229.                            aOtherDropsHandled) {
  230.     // do not report anything if all we did was drop an XPI
  231.     if ((aImportedInLibrary == 0) &&
  232.         (aInsertedInMediaList == 0) &&
  233.         (aDuplicates == 0) &&
  234.         (aOtherDropsHandled != 0)) 
  235.       return;
  236.     
  237.     // report different things depending on whether we dropped
  238.     // on a library, or just a list
  239.     if (aTargetList != aTargetList.library) {
  240.       DNDUtils.reportAddedTracks(aInsertedInMediaList, 
  241.                                  0, 
  242.                                  aTargetList.name);
  243.     } else {
  244.       DNDUtils.reportAddedTracks(aImportedInLibrary, 
  245.                                  aDuplicates, 
  246.                                  aTargetList.name);
  247.     }
  248.   },
  249.   
  250.   // --------------------------------------------------------------------------
  251.   // methods below this point are pretend-private
  252.   // --------------------------------------------------------------------------
  253.  
  254.   _stringbundle : null,
  255.   
  256.   // get a string from the default songbird bundle, with fallback
  257.   _getString: function(name, defaultValue) {
  258.     if (!this._stringbundle) {
  259.       var src = "chrome://songbird/locale/songbird.properties";
  260.       var stringBundleService = this._Cc["@mozilla.org/intl/stringbundle;1"]
  261.                                     .getService(this._Ci.nsIStringBundleService);
  262.       this._stringbundle = stringBundleService.createBundle(src);
  263.     }
  264.  
  265.     try {
  266.       return this._stringbundle.GetStringFromName(name);
  267.     }
  268.     catch(e) {
  269.       return defaultValue;
  270.     }
  271.   }
  272. }
  273.  
  274. /* MediaListViewSelectionTransferContext
  275.  *
  276.  * Create a drag and drop context containing the selected items in the view
  277.  * which is passed in.
  278.  * Implements: sbIMediaItemsTransferContext
  279.  */
  280. DNDUtils.MediaListViewSelectionTransferContext = function (mediaListView) {
  281.     this.items          = null; // filled in during reset()
  282.     this.indexedItems   = null;
  283.     this.source         = mediaListView.mediaList;
  284.     this.count          = mediaListView.selection.count;
  285.     this._mediaListView = mediaListView;
  286.     this.reset();
  287. };
  288. DNDUtils.MediaListViewSelectionTransferContext.prototype = {
  289.     reset: function() {
  290.       // Create an enumerator that unwraps the sbIIndexedMediaItem enumerator
  291.       // which selection provides.
  292.       var enumerator = this._mediaListView.selection.selectedIndexedMediaItems;
  293.       this.items = {
  294.         hasMoreElements : function() {
  295.           return enumerator.hasMoreElements();
  296.         },
  297.         getNext : function() {
  298.           var item = enumerator.getNext().mediaItem
  299.                        .QueryInterface(Components.interfaces.sbIMediaItem);
  300.           
  301.           item.setProperty(SBProperties.downloadStatusTarget,
  302.                            item.library.guid + "," + item.guid);
  303.           return item;
  304.         },
  305.         QueryInterface : function(iid) {
  306.           if (iid.equals(Components.interfaces.nsISimpleEnumerator) ||
  307.               iid.equals(Components.interfaces.nsISupports))
  308.             return this;
  309.           throw Components.results.NS_NOINTERFACE;
  310.         }
  311.       };
  312.  
  313.       // and here's the wrapped form for those cases where you want it
  314.       this.indexedItems = this._mediaListView.selection.selectedIndexedMediaItems;
  315.     },
  316.     QueryInterface : function(iid) {
  317.       if (iid.equals(Components.interfaces.sbIMediaItemsTransferContext) ||
  318.           iid.equals(Components.interfaces.nsISupports))
  319.         return this;
  320.       throw Components.results.NS_NOINTERFACE;
  321.     }
  322.   };
  323.   
  324. /* EntireMediaListViewTransferContext
  325.  *
  326.  * Create a drag and drop context containing all the items in the view
  327.  * which is passed in.
  328.  * Implements: sbIMediaItemsTransferContext
  329.  */
  330. DNDUtils.EntireMediaListViewTransferContext = function(view) {
  331.     this.items          = null;
  332.     this.indexedItems   = null;
  333.     this.source         = view.mediaList;
  334.     this.count          = view.length;
  335.     this._mediaListView = view;
  336.     this.reset();
  337.   }
  338. DNDUtils.EntireMediaListViewTransferContext.prototype = {
  339.   reset: function() {
  340.     // Create an ugly pseudoenumerator
  341.     var that = this;
  342.     this.items = {
  343.       i: 0,
  344.       hasMoreElements : function() {
  345.         return this.i < that._mediaListView.length;
  346.       },
  347.       getNext : function() {
  348.         var item = that._mediaListView.getItemByIndex(this.i++);
  349.         item.setProperty(SBProperties.downloadStatusTarget,
  350.                          item.library.guid + "," + item.guid);
  351.         return item;
  352.       },
  353.       QueryInterface : function(iid) {
  354.         if (iid.equals(Components.interfaces.nsISimpleEnumerator) ||
  355.             iid.equals(Components.interfaces.nsISupports))
  356.           return this;
  357.         throw Components.results.NS_NOINTERFACE;
  358.       }
  359.     };
  360.   },
  361.   QueryInterface : function(iid) {
  362.     if (iid.equals(Components.interfaces.sbIMediaItemsTransferContext) ||
  363.         iid.equals(Components.interfaces.nsISupports))
  364.       return this;
  365.     throw Components.results.NS_NOINTERFACE;
  366.   }
  367. };
  368.  
  369. /* MediaListTransferContext
  370.  *
  371.  * A transfer context suitable for moving a single media list around the system.
  372.  * As of this writing, the only place to create a drag/drop session of a single
  373.  * media list is the service pane, though it is also possible that extension
  374.  * developers will create them in the playlist widget.
  375.  */
  376. DNDUtils.MediaListTransferContext = function (item, mediaList) {
  377.     this.item   = item;
  378.     this.list   = item;
  379.     this.source = mediaList;
  380.     this.count  = 1;
  381.   }
  382. DNDUtils.MediaListTransferContext.prototype = {
  383.     QueryInterface : function(iid) {
  384.       if (iid.equals(Components.interfaces.sbIMediaListTransferContext) ||
  385.           iid.equals(Components.interfaces.nsISupports))
  386.         return this;
  387.       throw Components.results.NS_NOINTERFACE;
  388.     }
  389.   }
  390.  
  391. /*
  392.  
  393.   InternalDropHandler
  394.   
  395.   
  396. This helper is used to let you handle internal drops (mediaitems and medialists)
  397. and inject the items into a medialist, potentially at a specific position.
  398.  
  399. There are two ways of triggering a drop handling, the question of which one you
  400. should be using depends on how it is you would like to handle the drop:
  401.  
  402. To handle a drop in a generic manner, and have all dropped items automatically
  403. directed to the default library, all you need to do is add the following code in
  404. your onDrop/ondragdrop handler:
  405.  
  406.   InternalDropHandler.drop(window, dragSession, dropHandlerListener);
  407.  
  408. The last parameter is optional, it allows you to receive notifications. Here is
  409. a minimal implementation:
  410.  
  411.   var dropHandlerListener = {
  412.     // called when the drop handling has completed
  413.     onDropComplete: function(aTargetList,
  414.                              aImportedInLibrary,
  415.                              aDuplicates,
  416.                              aInsertedInMediaList,
  417.                              aOtherDropsHandled) { 
  418.       // returning true causes the standard drop report to be printed
  419.       // on the status bar, it is equivalent to calling standardReport
  420.       // using the parameters received on this callback
  421.       return true; 
  422.     },
  423.     // called when the first item is handled (eg, to implement playback)
  424.     onFirstMediaItem: function(aTargetList, aFirstMediaItem) { }
  425.     // called when a medialist has been copied from a different source library
  426.     onCopyMediaList: function(aSourceList, aNewList) { }
  427.   };
  428.  
  429. To handle a drop with a specific mediaList target and drop insertion point, use
  430. the following code:
  431.  
  432.   InternalDropHandler.dropOnList(window, 
  433.                                  dragSession, 
  434.                                  targetMediaList, 
  435.                                  targetMediaListPosition,
  436.                                  dropHandlerListener);
  437.  
  438. In order to target the drop at the end of the targeted mediaList, you
  439. should give a value of -1 for targetMediaListPosition.
  440.  
  441. The other public methods in this helper can be used to simplify the rest of your
  442. drag and drop handler as well. For instance, an nsDragAndDrop observer's 
  443. getSupportedFlavours() method may be implemented simply as:
  444.  
  445.     var flavours = new FlavourSet();
  446.     InternalDropHandler.addFlavours(flavours);
  447.     return flavours;
  448.  
  449. Also, getTransferData, getInternalTransferDataForFlavour, and 
  450. getTransferDataForFlavour may be used to inspect the content of the dragSession 
  451. before deciding what to do with it.
  452.  
  453. */
  454.  
  455. const TYPE_X_SB_TRANSFER_MEDIA_ITEM = "application/x-sb-transfer-media-item";
  456. const TYPE_X_SB_TRANSFER_MEDIA_LIST = "application/x-sb-transfer-media-list";
  457. const TYPE_X_SB_TRANSFER_MEDIA_ITEMS = "application/x-sb-transfer-media-items";
  458.  
  459. var InternalDropHandler = {
  460.  
  461.   _Ci: Components.interfaces,
  462.   _Cc: Components.classes,
  463.  
  464.   supportedFlavours: [ TYPE_X_SB_TRANSFER_MEDIA_ITEM,
  465.                        TYPE_X_SB_TRANSFER_MEDIA_LIST,
  466.                        TYPE_X_SB_TRANSFER_MEDIA_ITEMS ],
  467.   
  468.   // returns true if the drag session contains supported internal flavors
  469.   isSupported: function(aDragSession) {
  470.     return DNDUtils.isSupported(aDragSession, this.supportedFlavours);
  471.   },
  472.   
  473.   // performs a default drop of the drag session. media items go to the
  474.   // main library.
  475.   drop: function(aWindow, aDragSession, aListener) {
  476.     var mainLibrary = this._Cc["@songbirdnest.com/Songbird/library/Manager;1"]
  477.                           .getService(this._Ci.sbILibraryManager)
  478.                           .mainLibrary;
  479.     this.dropOnList(aWindow, aDragSession, mainLibrary, -1, aListener);
  480.   },
  481.  
  482.   // perform a drop onto a medialist. media items are inserted at the specified
  483.   // position in the list if that list is orderable. otherwise, or if the
  484.   // position is invalid, the items are added to the target list.
  485.   dropOnList: function(aWindow, 
  486.                        aDragSession, 
  487.                        aTargetList, 
  488.                        aDropPosition, 
  489.                        aListener) {
  490.     if (!aTargetList) {
  491.       throw new Error("No target medialist specified for dropOnList");
  492.     }
  493.     this._dropItems(aWindow,
  494.                     aDragSession, 
  495.                     aTargetList, 
  496.                     aDropPosition, 
  497.                     aListener);
  498.   },
  499.  
  500.   // call this to automatically add the supported internal flavors 
  501.   // to a flavourSet object
  502.   addFlavours: function(aFlavourSet) {
  503.     DNDUtils.addFlavours(aFlavourSet, this.supportedFlavours);
  504.   },
  505.  
  506.   // returns an array with the data for any supported internal flavor
  507.   getTransferData: function(aSession) {
  508.     return DNDUtils.getTransferData(aSession, this.supportedFlavours);
  509.   },
  510.   
  511.   // simply forward the call. here in this object for completeness
  512.   // see DNDUtils.getTransferDataForFlavour for more info
  513.   getTransferDataForFlavour: function(aFlavour, aSession, aArray) {
  514.     return DNDUtils.getTransferDataForFlavour(aFlavour, aSession, aArray);
  515.   },
  516.  
  517.   // --------------------------------------------------------------------------
  518.   // methods below this point are pretend-private
  519.   // --------------------------------------------------------------------------
  520.   
  521.   _dropItems: function(window, session, targetList, aDropPosition, aListener) {
  522.  
  523.     // are we dropping a media list ?
  524.     if (session.isDataFlavorSupported(TYPE_X_SB_TRANSFER_MEDIA_LIST)) {
  525.       
  526.       var context = DNDUtils.
  527.         getInternalTransferDataForFlavour(session,
  528.                                           TYPE_X_SB_TRANSFER_MEDIA_LIST, 
  529.                                           this._Ci.sbIMediaListTransferContext);
  530.       var list = context.list;
  531.  
  532.       // record metrics
  533.       var metrics_totype = targetList.library.getProperty(SBProperties.customType);
  534.       var metrics_fromtype = list.library.getProperty(SBProperties.customType);
  535.       this._metrics("app.servicepane.copy", 
  536.                          metrics_fromtype, 
  537.                          metrics_totype, 
  538.                          list.length);
  539.  
  540.       // are we dropping on a library ?
  541.       var targetListIsLibrary = (targetList instanceof this._Ci.sbILibrary);
  542.  
  543.       if (targetListIsLibrary) {
  544.         // want to copy the list and the contents
  545.         if (targetList == list.library) {
  546.           return;
  547.         }
  548.  
  549.         // create a copy of the list
  550.         var newlist = targetList.copyMediaList('simple', list);
  551.         
  552.         if (aListener) {
  553.           aListener.onCopyMediaList(targetList, newlist);
  554.           if (newlist.length > 0)
  555.             aListener.onFirstMediaItem(newlist.getItemByIndex(0));
  556.         }
  557.  
  558.       } else {
  559.         if (context.list == targetList) {
  560.           // uh oh - you can't drop a list onto itself
  561.           return;
  562.         }
  563.         // add the contents
  564.         if (aDropPosition != -1 &&
  565.             targetList instanceof this._Ci.sbIOrderableMediaList) {
  566.  
  567.           // make an enumerator with all the items from the source playlist
  568.           var allItems = {
  569.             items: [],
  570.             onEnumerationBegin: function(aMediaList) {
  571.               this.items = Cc["@songbirdnest.com/moz/xpcom/threadsafe-array;1"]
  572.                              .createInstance(Ci.nsIMutableArray);
  573.             },
  574.             onEnumeratedItem: function(aMediaList, aMediaItem) {
  575.               this.items.appendElement(aMediaItem, false);
  576.             },
  577.             onEnumerationEnd: function(aMediaList, aResultCode) {
  578.             }
  579.           };
  580.  
  581.           list.enumerateAllItems(allItems);
  582.  
  583.           targetList.insertSomeBefore(aDropPosition, allItems.items.enumerate());
  584.         } else {
  585.           targetList.addAll(list);
  586.         }
  587.         if (aListener && list.length > 0) {
  588.           aListener.onFirstMediaItem(list.getItemByIndex(0));
  589.         }
  590.       }
  591.       // lone> some of these values may not be accurate, this assumes that all 
  592.       // tracks have been copied, which is true if both the source and target 
  593.       // are playlists, but could be false if the target is a library. better 
  594.       // than nothing anyway.
  595.       this._dropComplete(aListener, targetList, list.length, 0, list.length, 0);
  596.  
  597.     } else if (session.isDataFlavorSupported(TYPE_X_SB_TRANSFER_MEDIA_ITEMS)) {
  598.  
  599.       var context = DNDUtils.
  600.         getInternalTransferDataForFlavour(session,
  601.                                           TYPE_X_SB_TRANSFER_MEDIA_ITEMS, 
  602.                                           this._Ci.sbIMediaItemsTransferContext);
  603.  
  604.       var items = context.items;
  605.       
  606.       // are we dropping on a library ?
  607.       if (targetList instanceof this._Ci.sbILibrary) {
  608.         if (items.hasMoreElements() && 
  609.             items.getNext().library == targetList) {
  610.           // can't add items to a library to which they already belong
  611.           this._dropComplete(aListener, targetList, 0, context.count, 0, 0);
  612.           return;
  613.         }
  614.         // we just ate an item; reset the enumerator
  615.         context.reset();
  616.         items = context.items;
  617.       }
  618.  
  619.       // record metrics
  620.       var metrics_totype = targetList.library.getProperty(SBProperties.customType);
  621.       var metrics_fromtype = context.source.library.getProperty(SBProperties.customType);
  622.       this._metrics("app.servicepane.copy", 
  623.                          metrics_fromtype, 
  624.                          metrics_totype, 
  625.                          context.count);
  626.  
  627.       // Create an enumerator that wraps the enumerator we were handed.
  628.       // We use this to set downloadStatusTarget and to notify the onFirstMediaItem
  629.       // listener.
  630.       var unwrapper = {
  631.         enumerator: items,
  632.         first: true,
  633.  
  634.         hasMoreElements : function() {
  635.           return this.enumerator.hasMoreElements();
  636.         },
  637.         getNext : function() {
  638.           var item = this.enumerator.getNext();
  639.           
  640.           if (this.first) {
  641.             this.first = false;
  642.             if (aListener)
  643.               aListener.onFirstMediaItem(item);
  644.           }
  645.           
  646.           item.setProperty(SBProperties.downloadStatusTarget,
  647.                            item.library.guid + "," + item.guid);
  648.           return item;
  649.         },
  650.         QueryInterface : function(iid) {
  651.           if (iid.equals(Components.interfaces.nsISimpleEnumerator) ||
  652.               iid.equals(Components.interfaces.nsISupports))
  653.             return this;
  654.           throw Components.results.NS_NOINTERFACE;
  655.         }
  656.       }
  657.  
  658.       if (aDropPosition != -1 &&
  659.               targetList instanceof this._Ci.sbIOrderableMediaList) {
  660.         targetList.insertSomeBefore(unwrapper, aDropPosition);
  661.       } else {
  662.         targetList.addSome(items);
  663.       }
  664.       
  665.       this._dropComplete(aListener, targetList, context.count, 0, context.count, 0);
  666.     }
  667.   },
  668.  
  669.   // called when the whole drop handling operation has completed, used
  670.   // to notify the original caller and free up any resources we can
  671.   _dropComplete: function(listener,
  672.                           targetList, 
  673.                           totalImported, 
  674.                           totalDups, 
  675.                           totalInserted,
  676.                           otherDrops) {
  677.     // notify the listener that we're done
  678.     if (listener) {
  679.       if (listener.onDropComplete(targetList,
  680.                                   totalImported,
  681.                                   totalDups, 
  682.                                   totalInserted,
  683.                                   otherDrops)) {
  684.         DNDUtils.standardReport(targetList,
  685.                                 totalImported,
  686.                                 totalDups, 
  687.                                 totalInserted,
  688.                                 otherDrops);
  689.       }
  690.     } else {
  691.       DNDUtils.standardReport(targetList,
  692.                               totalImported,
  693.                               totalDups, 
  694.                               totalInserted,
  695.                               otherDrops);
  696.     }
  697.   },
  698.   
  699.   _metrics: function(aCategory, aUniqueID, aExtraString, aIntValue) {
  700.     var metrics = Components.classes["@songbirdnest.com/Songbird/Metrics;1"]
  701.                       .createInstance(this._Ci.sbIMetrics);
  702.     metrics.metricsAdd(aCategory, aUniqueID, aExtraString, aIntValue);
  703.   }
  704.  
  705. }
  706.  
  707. /*
  708.  
  709.  
  710.   ExternalDropHandler JSM Module
  711.  
  712.  
  713.  
  714. This helper is used to let you handle external file drops and automatically
  715. handle scanning directories as needed, injecting items at a specific spot in
  716. a media list, schedule a metadata scanner job for the newly imported items,
  717. and so on.
  718.  
  719. There are two ways of triggering a drop handling, the question of which one you
  720. should be using depends on how it is you would like to handle the drop:
  721.  
  722. To handle a drop in a generic manner, and have all dropped items automatically
  723. directed to the default library, all you need to do is add the following code in
  724. your onDrop/ondragdrop handler:
  725.  
  726.   ExternalDropHandler.drop(window, dragSession, dropHandlerListener);
  727.  
  728. The last parameter is optional, it allows you to receive notifications. Here is
  729. a minimal implementation:
  730.  
  731.   var dropHandlerListener = {
  732.     // called when the drop handling has completed
  733.     onDropComplete: function(aTargetList,
  734.                              aImportedInLibrary,
  735.                              aDuplicates,
  736.                              aInsertedInMediaList,
  737.                              aOtherDropsHandled) { 
  738.       // returning true causes the standard drop report to be printed
  739.       // on the status bar, it is equivalent to calling standardReport
  740.       // using the parameters received on this callback
  741.       return true; 
  742.     },
  743.     // called when the first item is handled (eg, to implement playback)
  744.     onFirstMediaItem: function(aTargetList, aFirstMediaItem) { }
  745.   };
  746.  
  747. To handle a drop with a specific mediaList target and drop insertion point, use
  748. the following code:
  749.  
  750.   ExternalDropHandler.dropOnList(window, 
  751.                                  dragSession, 
  752.                                  targetMediaList, 
  753.                                  targetMediaListPosition,
  754.                                  dropHandlerListener);
  755.  
  756. In order to target the drop at the end of the targeted mediaList, you
  757. should give a value of -1 for targetMediaListPosition.
  758.  
  759. Two similar methods (dropUrls and dropUrlsOnList) exist that let you simulate a 
  760. drop by giving a list of URLs, and triggering the same handling as the one that 
  761. would happen had these URLs been part of a dragsession drop.
  762.  
  763. The other public methods in this helper can be used to simplify the rest of your
  764. drag and drop handler as well. For instance, an nsDragAndDrop observer's 
  765. getSupportedFlavours() method may be implemented simply as:
  766.  
  767.     var flavours = new FlavourSet();
  768.     ExternalDropHandler.addFlavours(flavours);
  769.     return flavours;
  770.  
  771. Also, getTransferData and DNDUtils.getTransferDataForFlavour may be used to 
  772. inspect the content of the dragSession before deciding what to do with it.
  773.  
  774. Finally, the standardReport and reportAddedTracks methods are used to send a 
  775. temporary message on the status bar, to report the result of a drag and drop 
  776. session. standardReport will format the text using the specific rules for what
  777. to show and in which circumstances, and reportAddedTracks will report exactly
  778. what you tell it to.
  779.  
  780. Important note:
  781. ---------------
  782.  
  783. The window being passed as a parameter to both the drop and dropOnList methods
  784. must implement the following two functions :
  785.  
  786. SBOpenModalDialog(aChromeUrl, aTargetId, aWindowFeatures, aWindowArguments);
  787. installXPI(aXpiUrl);
  788.  
  789. These two methods are respectively implemented in windowUtils.js and 
  790. playerOpen.js, importing these scripts in your window ensures that the
  791. requirements are met.
  792.  
  793. */
  794.  
  795. var ExternalDropHandler = {
  796.  
  797.   _Ci: Components.interfaces,
  798.   _Cc: Components.classes,
  799.  
  800.   supportedFlavours: [ "application/x-moz-file",
  801.                        "text/x-moz-url",
  802.                        "text/unicode"],
  803.   
  804.   // returns true if the drag session contains supported external flavors
  805.   isSupported: function(aDragSession) {
  806.     return DNDUtils.isSupported(aDragSession, this.supportedFlavours);
  807.   },
  808.   
  809.   // performs a default drop of the drag session. media items go to the
  810.   // main library. 
  811.   drop: function(aWindow, aDragSession, aListener) {
  812.     var mainLibrary = this._Cc["@songbirdnest.com/Songbird/library/Manager;1"]
  813.                           .getService(this._Ci.sbILibraryManager)
  814.                           .mainLibrary;
  815.     this.dropOnList(aWindow, aDragSession, mainLibrary, -1, aListener);
  816.   },
  817.  
  818.   // performs a default drop of a list of urls. media items go to the
  819.   // main library. 
  820.   dropUrls: function(aWindow, aUrlArray, aListener) {
  821.     var mainLibrary = this._Cc["@songbirdnest.com/Songbird/library/Manager;1"]
  822.                           .getService(this._Ci.sbILibraryManager)
  823.                           .mainLibrary;
  824.     this.dropUrlsOnList(aWindow, aUrlArray, mainLibrary, -1, aListener);
  825.   },
  826.  
  827.   // perform a drop onto a medialist. media items are inserted at the specified
  828.   // position in the list if that list is orderable. otherwise, or if the
  829.   // position is invalid, the items are added to the target list.
  830.   dropOnList: function(aWindow, 
  831.                        aDragSession, 
  832.                        aTargetList, 
  833.                        aDropPosition, 
  834.                        aListener) {
  835.     if (!aTargetList) {
  836.       throw new Error("No target medialist specified for dropOnList");
  837.     }
  838.     this._dropFiles(aWindow,
  839.                     aDragSession, 
  840.                     null,
  841.                     aTargetList, 
  842.                     aDropPosition, 
  843.                     aListener);
  844.   },
  845.  
  846.   // perform a drop of a list of urls onto a medialist. media items are inserted at 
  847.   // the specified position in the list if that list is orderable. otherwise, or if the
  848.   // position is invalid, the items are added to the target list.
  849.   dropUrlsOnList: function(aWindow, 
  850.                            aUrlArray, 
  851.                            aTargetList, 
  852.                            aDropPosition, 
  853.                            aListener) {
  854.     if (!aTargetList) {
  855.       throw new Error("No target medialist specified for dropOnList");
  856.     }
  857.     this._dropFiles(aWindow,
  858.                     null, 
  859.                     aUrlArray,
  860.                     aTargetList, 
  861.                     aDropPosition, 
  862.                     aListener);
  863.   },
  864.  
  865.   // call this to automatically add the supported external flavors 
  866.   // to a flavourSet object
  867.   addFlavours: function(aFlavourSet) {
  868.     DNDUtils.addFlavours(aFlavourSet, this.supportedFlavours);
  869.   },
  870.  
  871.   // returns an array with the data for any supported external flavor
  872.   getTransferData: function(aSession) {
  873.     return DNDUtils.getTransferData(aSession, this.supportedFlavours);
  874.   },
  875.  
  876.   // simply forward the call. here in this object for completeness
  877.   // see DNDUtils.getTransferDataForFlavour for more info
  878.   getTransferDataForFlavour: function(aFlavour, aSession, aArray) {
  879.     return DNDUtils.getTransferDataForFlavour(aFlavour, aSession, aArray);
  880.   },
  881.  
  882.   // --------------------------------------------------------------------------
  883.   // methods below this point are pretend-private
  884.   // --------------------------------------------------------------------------
  885.  
  886.   _importInProgress    : false, // are we currently importing a drop ?
  887.   _fileList            : [],    // list of files URLs to import
  888.   _directoryList       : [],    // queue of nsIFile directories to scan
  889.   _targetList          : null,  // target mediaList for the drop
  890.   _targetPosition      : -1,    // position in the mediaList we should drop at
  891.   _firstMediaItem      : null,  // first mediaItem that was handled in this drop
  892.   _scanList            : null,  // list of newly created medaItems for metadata scan
  893.   _window              : null,  // window that received this drop
  894.   _listener            : null,  // listener object, for notifications
  895.   _totalImported       : 0,     // number of items imported in the library
  896.   _totalInserted       : 0,     // number of items inserted in the medialist
  897.   _totalDups           : 0,     // number of items we already had in the library
  898.   _otherDrops          : 0,     // number of other drops handled (eg, XPI, JAR)
  899.  
  900.   // initiate the handling of all dropped files: this handling is sliced up 
  901.   // into a number of 'frames', each frame importing one item, or queuing up 
  902.   // one directory for later import. at the end of each frame, the 
  903.   // _nextImportDropFrame method is called to schedule the next frame using a 
  904.   // short timer, so as to give the UI time to catch up, and we keep doing that 
  905.   // until everything in the file queue has been processed. when that's done, 
  906.   // we then look for queued directory scans, which we give to the directory 
  907.   // import service. after the directories have been processed, we notify the 
  908.   // listener that processing has ended. Note that the function can take either
  909.   // a drag session of an array of URLs. If both are provided, only the session
  910.   // will be handled (ie, the method is not meant to be called with both a session
  911.   // and a urlarray).
  912.   _dropFiles: function(window, session, urlarray, targetlist, position, listener) {
  913.   
  914.     // check that we are indeed processing an external drop
  915.     if (session && !this.isSupported(session)) {
  916.       return;
  917.     }
  918.  
  919.     // the array to record items to feed to the metadata scanner
  920.     if (!this._scanList) {
  921.       this._scanList = this._Cc["@songbirdnest.com/moz/xpcom/threadsafe-array;1"]
  922.                           .createInstance(this._Ci.nsIMutableArray);
  923.     }
  924.  
  925.     // if we are on win32, we will need to make local filenames lowercase
  926.     var lcase = (this._getPlatformString() == "Windows_NT");
  927.     
  928.     // get drop data in any of the supported formats
  929.     var dropdata = session ? this.getTransferData(session) : urlarray;
  930.     
  931.     // remember the target list and position for this drop
  932.     this._targetList = targetlist;
  933.     this._targetPosition = position;
  934.     
  935.     // reset first media item, so we know to record it again
  936.     this._firstMediaItem = null;
  937.     
  938.     // remember window for openModalDialog
  939.     this._window = window;
  940.     
  941.     // remember listener
  942.     this._listener = listener;
  943.     
  944.     // reset stats
  945.     this._totalImported = 0;
  946.     this._totalInserted = 0;
  947.     this._totalDups = 0;
  948.     
  949.     // process all entries in the drop
  950.     for (var dropentry in dropdata) {
  951.       var dropitem = dropdata[dropentry];
  952.     
  953.       var item, flavour;
  954.       if (session) {
  955.         item = dropitem[0];
  956.         flavour = dropitem[2];
  957.       } else {
  958.         item = dropitem;
  959.         flavour = "text/x-moz-url";
  960.       }
  961.       var islocal = true;
  962.       var rawData;
  963.       
  964.       var prettyName;
  965.  
  966.       if (flavour == "application/x-moz-file") {
  967.         var ioService = this._Cc["@mozilla.org/network/io-service;1"]
  968.                             .getService(this._Ci.nsIIOService);
  969.         var fileHandler = ioService.getProtocolHandler("file")
  970.                           .QueryInterface(this._Ci.nsIFileProtocolHandler);
  971.         rawData = fileHandler.getURLSpecFromFile(item);
  972.       } else {
  973.         if (item instanceof this._Ci.nsISupportsString) {
  974.           rawData = item.toString();
  975.         } else {
  976.           rawData = ""+item;
  977.         }
  978.         if (rawData.toLowerCase().indexOf("http://") >= 0) {
  979.           // remember that this is not a local file
  980.           islocal = false;
  981.         } else if (rawData.toLowerCase().indexOf("file://") >= 0) {
  982.           islocal = true;
  983.         } else {
  984.           // not a url, ignore
  985.           continue;
  986.         }
  987.       }
  988.       
  989.       // rawData contains a file or http URL to the dropped media.
  990.  
  991.       // check if there is a pretty name we can grab
  992.       var separator = rawData.indexOf("\n");
  993.       if (separator != -1) {
  994.         prettyName = rawData.substr(separator+1);
  995.         rawData = rawData.substr(0,separator);
  996.       }
  997.       
  998.       // make filename lowercase if necessary (win32)
  999.       if (lcase && islocal) {
  1000.         rawData = rawData.toLowerCase();
  1001.       }
  1002.  
  1003.       // record this file for later processing
  1004.       this._fileList.push(rawData);
  1005.     }
  1006.  
  1007.     // if we are already importing, our entries will be processed at the end
  1008.     // of the current batch, otherwise, we need to start a new one
  1009.     if (!this._importInProgress) {
  1010.       
  1011.       // remember that we are currently processing the drop.
  1012.       // if more files are dropped while we're importing, they will be added
  1013.       // to the end of the batch import
  1014.       this._importInProgress = true;
  1015.       
  1016.       // begin processing array of dropped items
  1017.       // (the first item will be handled immediately, and the subsequent
  1018.       // ones will be processed each time a new "frame" occurs, on a timer)
  1019.       this._importDropFrame();
  1020.     }
  1021.   },
  1022.   
  1023.   // give a little bit of time for the main thread to react to UI events, and 
  1024.   // then execute the next frame of the drop import session.
  1025.   _nextImportDropFrame: function() {
  1026.     this._timer = this._Cc["@mozilla.org/timer;1"]
  1027.                       .createInstance(this._Ci.nsITimer);
  1028.     this._timer.initWithCallback(this, 10, this._Ci.nsITimer.TYPE_ONE_SHOT);    
  1029.   },
  1030.  
  1031.   // one shot timer notification method
  1032.   notify: function(timer) {
  1033.     this._timer = null;
  1034.     this._importDropFrame();
  1035.   },
  1036.   
  1037.   // executes one frame of the drop import session
  1038.   _importDropFrame: function() {
  1039.     try {
  1040.       // any more files to process ?
  1041.       if (this._fileList.length > 0) {
  1042.         
  1043.         // get the first of the remaining files
  1044.         var url;
  1045.         url = this._fileList[0];
  1046.         
  1047.         // remove it from the array of files to process
  1048.         this._fileList.splice(0, 1);
  1049.         
  1050.         // safety check
  1051.         if (url != "") {
  1052.  
  1053.           // make a URI object for this file
  1054.           var ios = this._Cc["@mozilla.org/network/io-service;1"]
  1055.                         .getService(this._Ci.nsIIOService);
  1056.           var uri = ios.newURI(url, null, null);
  1057.           
  1058.           // make a file URL object and check if the object is a directory.
  1059.           // if it is a directory, then we record it in a separate array,
  1060.           // which we will process at the end of the batch.
  1061.           var fileUrl;
  1062.           try {
  1063.             fileUrl = uri.QueryInterface(this._Ci.nsIFileURL);
  1064.           } catch (e) { 
  1065.             fileUrl = null; 
  1066.           }
  1067.           if (fileUrl) {
  1068.             // is this is a directory?
  1069.             if (fileUrl.file.isDirectory()) {
  1070.               // yes, record it and delay processing
  1071.               this._directoryList.push(fileUrl.file);
  1072.               // continue with the current batch
  1073.               this._nextImportDropFrame();
  1074.               return;
  1075.             }
  1076.           }
  1077.  
  1078.           // process this item
  1079.           this._importDropFile(uri);
  1080.         }
  1081.  
  1082.         // continue with the current batch
  1083.         this._nextImportDropFrame();
  1084.  
  1085.       } else {
  1086.         
  1087.         // there are no more items in the drop array, start the metadata scanner
  1088.         // if we created any mediaitem
  1089.         if (this._scanList && 
  1090.             this._scanList.length > 0) {
  1091.           var metadataService = 
  1092.             this._Cc["@songbirdnest.com/Songbird/FileMetadataService;1"]
  1093.                 .getService(this._Ci.sbIFileMetadataService);
  1094.           metadataService.read(this._scanList);
  1095.           this._scanList = null;
  1096.         }
  1097.         
  1098.         // see if there are any directories to be scanned now.
  1099.         if (this._directoryList.length > 0) {
  1100.  
  1101.           // yes, there are, import them.
  1102.           this._importDropDirectories();
  1103.           
  1104.           // continue with the current batch, in case more stuff
  1105.           // has been dropped. this shouldnt be possible because the
  1106.           // mediascan dialog is modal, but it is better to check than
  1107.           // to lose drops
  1108.           this._nextImportDropFrame();
  1109.         } else {
  1110.           // all done.
  1111.           this._dropComplete();
  1112.         }
  1113.       }
  1114.     } catch (e) {
  1115.  
  1116.       // oops, something wrong happened, we do not want to abort the whole
  1117.       // import batch tho, so just print a debug message and try the next
  1118.       // frame
  1119.       Components.utils.reportError(e);
  1120.       this._nextImportDropFrame();
  1121.  
  1122.     }
  1123.   },
  1124.   
  1125.   // import the given file URI into the target library and optionally inserts in
  1126.   // into the target playlist at the desired spot. if the item already exists,
  1127.   // it is only inserted to the playlist
  1128.   _importDropFile: function(aURI) {
  1129.     try {    
  1130.       // is this a media url ?
  1131.       var gPPS = this._Cc["@songbirdnest.com/Songbird/PlaylistPlayback;1"]
  1132.                      .getService(this._Ci.sbIPlaylistPlayback);
  1133.       if (gPPS.isMediaURL( aURI.spec )) {
  1134.         
  1135.         // check whether the item already exists in the library 
  1136.         // for the target list
  1137.         var item = this._getFirstItemByProperty(this._targetList.library, 
  1138.                         "http://songbirdnest.com/data/1.0#contentURL", 
  1139.                         aURI.spec);
  1140.         // If we didn't find the content URL try the originURL
  1141.         if (!item) {
  1142.             item = this._getFirstItemByProperty(this._targetList.library, 
  1143.                                                 "http://songbirdnest.com/data/1.0#originURL", 
  1144.                                                 aURI.spec);
  1145.         }
  1146.         
  1147.         // if the item didnt exist before, create it now
  1148.         var itemAdded = false;
  1149.         if (!item) {
  1150.           var holder = {};
  1151.           var wasCreated = this._targetList
  1152.                                .library
  1153.                                .createMediaItemIfNotExist(aURI, null, holder);
  1154.           item = holder.value;
  1155.           
  1156.           // Ensure we have an item and that it was created before 'currentTime'.
  1157.           // We check the creation time specifically because items created before
  1158.           // 'currentTime' were not created but rather returned to us.
  1159.           if (wasCreated && item) {
  1160.             
  1161.             this._scanList.appendElement(item, false);
  1162.             this._totalImported++;
  1163.             
  1164.             itemAdded = true;
  1165.           }
  1166.         } 
  1167.  
  1168.         // The item was not added because it was a duplicate.
  1169.         if(!itemAdded) {
  1170.           this._totalDups++;
  1171.         }
  1172.  
  1173.         // if the item is valid, and we are inserting in a medialist, insert it 
  1174.         // to the requested position
  1175.         if (item) {
  1176.           if ((this._targetList instanceof this._Ci.sbIOrderableMediaList) &&
  1177.               (this._targetPosition >= 0) && 
  1178.               (this._targetPosition < this._targetList.length)) {
  1179.             this._targetList.insertBefore(this._targetPosition, item);
  1180.             this._targetPosition++;
  1181.           } else {
  1182.             this._targetList.add(item);
  1183.           }
  1184.           this._totalInserted++;
  1185.         }
  1186.         // report the first item that was dropped
  1187.         if (!this._firstMediaItem) {
  1188.           this._firstMediaItem = item;
  1189.           if (this._listener) {
  1190.             this._listener.onFirstMediaItem(this._targetList, item);
  1191.           }
  1192.         }
  1193.       } else if ( /\.(xpi|jar)$/i.test(aURI.spec) ) {
  1194.         this._otherDrops++;
  1195.         // this._window.installXPI(aURI.spec) will not work but this will:
  1196.         this._window.setTimeout(this._window.installXPI, 10, aURI.spec);
  1197.         // wicked! :D
  1198.       }
  1199.     } catch (e) {
  1200.       Components.utils.reportError(e);
  1201.     }
  1202.   },
  1203.   
  1204.   // search for an item inside a list based on a property value. this is used
  1205.   // by the drop handler to determine if a drop item is already in the target 
  1206.   // library, by looking for its contentURL.
  1207.   _getFirstItemByProperty: function(aMediaList, aProperty, aValue) {
  1208.     var listener = {
  1209.       item: null,
  1210.       onEnumerationBegin: function() {
  1211.       },
  1212.       onEnumeratedItem: function(list, item) {
  1213.         this.item = item;
  1214.         return Components.interfaces.sbIMediaListEnumerationListener.CANCEL;
  1215.       },
  1216.       onEnumerationEnd: function() {
  1217.       }
  1218.     };
  1219.  
  1220.     aMediaList.enumerateItemsByProperty(aProperty,
  1221.                                         aValue,
  1222.                                         listener);
  1223.     return listener.item;
  1224.   },
  1225.  
  1226.   // trigger the import of all directory entries that have been defered until
  1227.   // this point. this launches the media scanner dialog box.
  1228.   _importDropDirectories: function() {
  1229.     var importService = Cc['@songbirdnest.com/Songbird/DirectoryImportService;1']
  1230.                           .getService(Ci.sbIDirectoryImportService);
  1231.     
  1232.     var job = importService.import(ArrayConverter.nsIArray(this._directoryList),
  1233.                                    this._targetList, this._targetPosition);
  1234.     
  1235.     // Reset list of directories
  1236.     this._directoryList = [];
  1237.                                    
  1238.     SBJobUtils.showProgressDialog(job, this._window, /** No delay **/ 0);
  1239.     
  1240.     // If this job provided the first item, we may want to play it
  1241.     if (!this._firstMediaItem) {
  1242.       var allItems = job.enumerateAllItems();
  1243.       if (allItems.hasMoreElements()) {
  1244.         this._firstMediaItem = allItems.getNext().QueryInterface(Ci.sbIMediaItem);
  1245.         if (this._listener) {
  1246.           this._listener.
  1247.             onFirstMediaItem(this._targetList, this._firstMediaItem);
  1248.         }
  1249.       }
  1250.     }
  1251.  
  1252.     this._totalImported += job.totalAddedToLibrary;
  1253.     this._totalInserted += job.totalAddedToMediaList;
  1254.     this._totalDups += job.totalDuplicates;
  1255.   },
  1256.   
  1257.   // called when the whole drop handling operation has completed, used
  1258.   // to notify the original caller and free up any resources we can
  1259.   _dropComplete: function() {
  1260.     // notify the listener that we're done
  1261.     if (this._listener) {
  1262.       if (this._listener.onDropComplete(this._targetList,
  1263.                                         this._totalImported,
  1264.                                         this._totalDups, 
  1265.                                         this._totalInserted,
  1266.                                         this._otherDrops)) {
  1267.         DNDUtils.standardReport(this._targetList,
  1268.                                 this._totalImported,
  1269.                                 this._totalDups, 
  1270.                                 this._totalInserted,
  1271.                                 this._otherDrops);
  1272.       }
  1273.     } else {
  1274.       DNDUtils.standardReport(this._targetList,
  1275.                               this._totalImported,
  1276.                               this._totalDups, 
  1277.                               this._totalInserted,
  1278.                               this._otherDrops);
  1279.     }
  1280.     
  1281.     // and reset references we do not need anymore, coz leaks suck
  1282.     this._importInProgress = false;
  1283.     this._targetList = null;
  1284.     this._scanList = null;
  1285.     this._window = null;
  1286.     this._listener = null;
  1287.   },
  1288.   
  1289.   // returns the platform string
  1290.   _getPlatformString: function() {
  1291.     try {
  1292.       var sysInfo =
  1293.         Components.classes["@mozilla.org/system-info;1"]
  1294.                   .getService(Components.interfaces.nsIPropertyBag2);
  1295.       return sysInfo.getProperty("name");
  1296.     }
  1297.     catch (e) {
  1298.       var user_agent = navigator.userAgent;
  1299.       if (user_agent.indexOf("Windows") != -1)
  1300.         return "Windows_NT";
  1301.       else if (user_agent.indexOf("Mac OS X") != -1)
  1302.         return "Darwin";
  1303.       else if (user_agent.indexOf("Linux") != -1)
  1304.         return "Linux";
  1305.       else if (user_agent.indexOf("SunOS") != -1)
  1306.         return "SunOS";
  1307.       return "";
  1308.     }
  1309.   }
  1310.  
  1311. }
  1312.  
  1313.  
  1314.  
  1315.  
  1316.